package org.jeecg.modules.gis.util.network;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.geotools.graph.path.DijkstraShortestPathFinder;
import org.geotools.graph.path.Path;
import org.geotools.graph.structure.Edge;
import org.geotools.graph.structure.Graph;
import org.geotools.graph.structure.Node;
import org.geotools.graph.traverse.standard.DijkstraIterator;
import org.jeecg.modules.gis.entity.GisRoadNetwork;
import org.jeecg.modules.gis.entity.GisRoadNetworkEx;
import org.jeecg.modules.gis.util.GeoJsonJavaBeanUtil;
import org.jeecg.modules.gis.vo.RoutePraVo;
import org.locationtech.jts.algorithm.distance.DistanceToPoint;
import org.locationtech.jts.algorithm.distance.PointPairDistance;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.linearref.LinearLocation;
import org.locationtech.jts.linearref.LocationIndexedLine;
import org.locationtech.jts.operation.linemerge.LineMerger;
import org.opengis.feature.simple.SimpleFeature;

import java.util.*;

public class RoadNetworkModel {

    private final List<GisRoadNetworkEx> rawData;
    private final GeometryFactory geometryFactory;

    // 路径计算使用的边权重（当前以线长度为权重）
    private final DijkstraIterator.EdgeWeighter weighter = edge -> {
        SimpleFeature feature = (SimpleFeature) edge.getObject();
        Geometry geometry = (Geometry) feature.getDefaultGeometry();
        return geometry.getLength();
    };

    /**
     * 构造函数，传入 GeometryFactory 和初始路网数据（多线要素会被拆分）
     */
    public RoadNetworkModel(List<GisRoadNetwork> lines, GeometryFactory geometryFactory) {
        this.geometryFactory = geometryFactory;
        this.rawData = new ArrayList<>();
        for (GisRoadNetwork road : lines) {
            for (int j = 0; j < road.getGeom().getNumGeometries(); j++) {
                GisRoadNetworkEx ex = new GisRoadNetworkEx();
                ex.setId(road.getId() + j);
                ex.setGeom((LineString) road.getGeom().getGeometryN(j));
                this.rawData.add(ex);
            }
        }
    }

    /**
     * 获取原始数据
     */
    public List<GisRoadNetworkEx> getRawData() {
        return rawData;
    }

    /**
     * 找到与给定点最近的网络点
     */
    public Coordinate findNearestPoint(Coordinate coord) {
        Coordinate nearest = null;
        double minDistance = Double.MAX_VALUE;
        for (GisRoadNetworkEx ex : rawData) {
            Coordinate candidate = NetWorkUtil.findNearestPoint(ex.getGeom(), coord);
            double dist = candidate.distance(coord);
            if (dist < minDistance) {
                minDistance = dist;
                nearest = candidate;
            }
        }
        return nearest;
    }

    /**
     * 将线拆分为两段
     */
    private void splitLine(GisRoadNetworkEx road, Coordinate splitCoord, List<GisRoadNetworkEx> roads) {
        LineString line = road.getGeom();
        LocationIndexedLine indexedLine = new LocationIndexedLine(line);
        LinearLocation start = indexedLine.indexOf(line.getStartPoint().getCoordinate());
        LinearLocation end = indexedLine.indexOf(line.getEndPoint().getCoordinate());
        LinearLocation split = indexedLine.indexOf(splitCoord);

        LineString part1 = (LineString) indexedLine.extractLine(start, split);
        LineString part2 = (LineString) indexedLine.extractLine(split, end);

        roads.remove(road);

        GisRoadNetworkEx ex1 = new GisRoadNetworkEx();
        ex1.setId(road.getId() + "_1");
        ex1.setGeom(part1);
        roads.add(ex1);

        GisRoadNetworkEx ex2 = new GisRoadNetworkEx();
        ex2.setId(road.getId() + "_2");
        ex2.setGeom(part2);
        roads.add(ex2);
    }

    /**
     * 在路网中加入起点和终点，返回更新后的线集合及最近点信息
     */
    private Map<String, Object> addNodesToNetwork(Coordinate startCoord, Coordinate endCoord) {
        List<GisRoadNetworkEx> updatedRoads = new ArrayList<>();
        GisRoadNetworkEx startRoad = null, endRoad = null;
        Coordinate nearestStart = null, nearestEnd = null;
        double minDistStart = Double.MAX_VALUE, minDistEnd = Double.MAX_VALUE;

        for (GisRoadNetworkEx road : rawData) {
            updatedRoads.add(road);

            PointPairDistance ppdStart = new PointPairDistance();
            PointPairDistance ppdEnd = new PointPairDistance();

            DistanceToPoint.computeDistance(road.getGeom(), startCoord, ppdStart);
            DistanceToPoint.computeDistance(road.getGeom(), endCoord, ppdEnd);

            if (ppdStart.getDistance() < minDistStart) {
                minDistStart = ppdStart.getDistance();
                nearestStart = ppdStart.getCoordinate(0);
                startRoad = road;
            }

            if (ppdEnd.getDistance() < minDistEnd) {
                minDistEnd = ppdEnd.getDistance();
                nearestEnd = ppdEnd.getCoordinate(0);
                endRoad = road;
            }
        }

        if (startRoad != null && endRoad != null && startRoad.getId().equals(endRoad.getId())) {
            LocationIndexedLine indexedLine = new LocationIndexedLine(startRoad.getGeom());
            LineString between = (LineString) indexedLine.extractLine(
                    indexedLine.indexOf(nearestStart),
                    indexedLine.indexOf(nearestEnd)
            );
            updatedRoads.remove(startRoad);
            GisRoadNetworkEx single = new GisRoadNetworkEx();
            single.setId(startRoad.getId() + "_s");
            single.setGeom(between);
            updatedRoads.add(single);
        } else {
            if (startRoad != null) splitLine(startRoad, nearestStart, updatedRoads);
            if (endRoad != null) splitLine(endRoad, nearestEnd, updatedRoads);
        }

        Map<String, Object> result = new HashMap<>();
        result.put("list", updatedRoads);
        result.put("snc", nearestStart);
        result.put("enc", nearestEnd);
        return result;
    }

    /**
     * 获取图中与坐标点匹配的起点终点节点
     */
    private Map<String, Node> findStartEndNodes(Graph graph, Coordinate start, Coordinate end) {
        Map<String, Node> result = new HashMap<>();
        for (Object obj : graph.getNodes()) {
            Node node = (Node) obj;
            Point pt = (Point) node.getObject();
            if (start.equals2D(pt.getCoordinate(), 1e-7)) result.put("A", node);
            if (end.equals2D(pt.getCoordinate(), 1e-7)) result.put("B", node);
        }
        return result;
    }

    /**
     * 主功能：路径查找
     */
    public JSONObject getShortestRoute(RoutePraVo vo) throws Exception {
        Coordinate startCoord = new Coordinate(vo.getStarLng(), vo.getStarLat());
        Coordinate endCoord = new Coordinate(vo.getEndLng(), vo.getEndLat());

        Map<String, Object> addedResult = addNodesToNetwork(startCoord, endCoord);
        List<GisRoadNetworkEx> roads = (List<GisRoadNetworkEx>) addedResult.get("list");

        Graph graph = NetWorkUtil.buildGraph(roads);
        Map<String, Node> abNodes = findStartEndNodes(graph, (Coordinate) addedResult.get("snc"), (Coordinate) addedResult.get("enc"));

        DijkstraShortestPathFinder finder = new DijkstraShortestPathFinder(graph, abNodes.get("A"), weighter);
        finder.calculate();

        Path path = finder.getPath(abNodes.get("B"));

        LineMerger merger = new LineMerger();
        for (Object edgeObj : path.getEdges()) {
            Edge edge = (Edge) edgeObj;
            SimpleFeature feature = (SimpleFeature) edge.getObject();
            merger.add((Geometry) feature.getDefaultGeometry());
        }

        Collection merged = merger.getMergedLineStrings();
        LineString routeLine = (LineString) merged.iterator().next();

        // 确保方向从起点到终点
        if (startCoord.distance(routeLine.getStartPoint().getCoordinate()) >
                startCoord.distance(routeLine.getEndPoint().getCoordinate())) {
            routeLine = (LineString) routeLine.reverse();
        }

        GisRoadNetworkEx result = new GisRoadNetworkEx();
        result.setGeom(routeLine);
        return GeoJsonJavaBeanUtil.beanToFeature(result);
    }
}
